package com.bitsfromspace.ubertorrent.bencoding;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.bitsfromspace.ubertorrent.bencoding.BValue.Type;

/**
 * BEncoder encodes a list of {@link BValue} to a bencoded array of bytes. <br/>
 * {@link BValue}s can be added directly using {@link #add(BValue)}.<br/>
 * Other <em>adders</em> encode the java value to their {@link BValue} equivalent automatically.<br/>
 * Lists and Maps can be added using {@link #add(List)} and {@link #add(Map)}, all elements will be
 * converted to {@link BValue}s recursively.<br/>
 * {@link #encodeAsString()} will encode all previously added elements to a bencoding string.<br/>
 * Calling {@link #reset()} will remove all previously added values and allows reuse of this BEncoder.
 * @see BValue#wrap(Object)
 * @author: chris.oudeman
 * @since: Jan 5, 2011
 */
public class BEncoder {



	private final List<BValue> values;

	public BEncoder() {

		values = new ArrayList<BValue>();
	}

	/**
	 * Adds BValue to this encoder.
	 */
	public void add(BValue value) {

		values.add(value);
	}

	/**
	 * Adds a String to this encoder. The String will be internally converted to a {@link BValue} of type {@link Type#STRING}.
	 */
	public void add(String string) {

		add(new BValue(string));
	}

	/**
	 * Adds a Integer to this encoder. The Integer will be internally converted to a {@link BValue} of type {@link Type#INTEGER}.
	 */
	public void add(Integer integer) {

		add(integer.longValue());
	}

	/**
	 * Adds a Long to this encoder. The Long will be internally converted to a {@link BValue} of type {@link Type#INTEGER}.
	 */
	public void add(Long l) {

		add(new BValue(l));
	}

	/**
	 * Adds a List to this encoder. The List will be internally converted to a {@link BValue} of type {@link Type#LIST}.
	 * <br/>
	 * Any elements in the list will be converted to their {@link BValue} counterparts recursively.
	 * @see BValue#wrap(Object)
	 */
	public void add(List<?> list) {

		add(BValue.wrap(list));
	}

	/**
	 * Adds a Map to this encoder. The Map will be internally converted to a {@link BValue} of type {@link Type#MAP}.
	 * <br/>
	 * Any elements in the map will be converted to their {@link BValue} counterparts recursively.
	 * @see BValue#wrap(Object)
	 */
	public void add(Map<?, ?> map) {

		add(BValue.wrap(map));
	}

	/**
	 * Removes all previously added items.
	 */
	public void reset() {

		values.clear();
	}

	/**
	 * Encodes all added items to a bencoding string byte array. The bencoding string is 
	 * encodes as UTF-8.
	 */
	public byte[] encodeAsByteArray() {

		String string = encodeAsString();
		return string.getBytes();
	}

	/**
	 * Encodes all added items to a bencoding string.
	 */
	public String encodeAsString() {

		StringBuilder buffer = new StringBuilder();
		for (BValue value : values) {
			encode(buffer, value);
		}
		return buffer.toString();
	}

	/**
	 * Static helper method that encodes a {@link BValue} directly.
	 * @return The encoded byte string of {@link BValue} <em> value</em>.
	 */
	public static byte[] encode(BValue bvalue) {

		BEncoder bencoder = new BEncoder();
		bencoder.add(bvalue);
		return bencoder.encodeAsByteArray();
	}

	private void encode(StringBuilder buffer, BValue value) {

		switch (value.getType()) {
		case INTEGER:
			buffer.append("i").append(value.getInteger()).append("e");
			break;
		case STRING:
			buffer
					.append(value.getString().length()).append(":")
					.append(value.getString());
			break;
		case LIST:
			buffer.append("l");
			for (BValue listElement : value.getList()) {
				encode(buffer, listElement);
			}
			buffer.append("e");
			break;
		case MAP:
			buffer.append("d");
			for (Entry<BValue, BValue> entry : value.getMap().entrySet()) {
				encode(buffer, entry.getKey());
				encode(buffer, entry.getValue());
			}
			buffer.append("e");
			break;
		}

	}
}


